DESeq

loading data

For the DESeq2 analysis, the raw, prefiltered counts will be used.

se.gastroc <- readRDS("./data/Robjects/01_se.gastroc.rds")
se.soleus <- readRDS("./data/Robjects/01_se.soleus.rds")

counts.gastroc <- se.gastroc[rowData(se.gastroc)$filtered,] %>% assay()
counts.soleus <- se.soleus[rowData(se.soleus)$filtered,] %>% assay()
metadata <- colData(se.gastroc)

preparing DDS objects

createDDSObject <- function(counts, metadata) {
  # select sample columns
  reorder_index <- match(rownames(metadata), colnames(counts))
  counts <- counts[,reorder_index]
  
  # Check metadata consistency
  all(rownames(metadata) %in% colnames(counts)) %>%
    assertthat::assert_that(., msg = "metadata and count table do not match")
  
  ## DESeq2 object
  dds <- DESeqDataSetFromMatrix(countData = counts,
                                colData = metadata,
                                design = ~ genotype)
  
  return( DESeq(dds) )
}

# creating DESeq Objects
dds.gastroc <- createDDSObject(counts.gastroc, metadata)
dds.soleus <- createDDSObject(counts.soleus, metadata)

# creating SESeqg Objects from SE
# dds.gastroc.se <- DESeqDataSet(se.gastroc, design = ~ genotype)
# dds.soleus.se <- DESeqDataSet(se.soleus, design = ~ genotype)

Results

res.gastroc <- results(dds.gastroc, alpha = pCutoff, contrast = c("genotype", "KO", "WT"))
res.soleus <- results(dds.soleus, alpha = pCutoff, contrast = c("genotype", "KO", "WT"))

MA-Plots

gastroc

plotMA(res.gastroc)

soleus

plotMA(res.soleus)

Dispersion Estimates

gastroc

plotDispEsts(dds.gastroc)

soleus

plotDispEsts(dds.soleus)

top significant diff. genes

topGenes.gastroc <- as.data.frame(res.gastroc) %>%
  tibble::rownames_to_column("GeneID") %>%
  top_n(100, wt = -padj) %>%
  arrange(padj)

topGenes.soleus <- as.data.frame(res.soleus) %>%
  tibble::rownames_to_column("GeneID") %>%
  top_n(100, wt = -padj) %>%
  arrange(padj)

knitr::kable(head(topGenes.gastroc), caption = "gastroc")
gastroc
GeneID baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000050335 4934.2949 6.267396 0.1664065 37.66316 0 0
ENSMUSG00000081249 1378.5460 4.208222 0.1115955 37.70959 0 0
ENSMUSG00000010751 1864.4937 5.610357 0.2014267 27.85309 0 0
ENSMUSG00000032712 5136.5059 4.686418 0.1683204 27.84225 0 0
ENSMUSG00000034855 554.3475 7.276611 0.2735037 26.60517 0 0
ENSMUSG00000037613 960.7558 3.183338 0.1266510 25.13472 0 0
knitr::kable(head(topGenes.soleus), caption = "soleus")
soleus
GeneID baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000038615 32047.185 -2.5454285 0.0636829 -39.97038 0 0
ENSMUSG00000081249 1651.592 4.5759032 0.1292779 35.39587 0 0
ENSMUSG00000039067 4355.557 -1.1049116 0.0419786 -26.32084 0 0
ENSMUSG00000028932 3190.657 -1.1102258 0.0558536 -19.87741 0 0
ENSMUSG00000006998 7402.617 -0.9196943 0.0468631 -19.62511 0 0
ENSMUSG00000102869 6985.622 -0.9181929 0.0479924 -19.13203 0 0

p-values

hist(res.gastroc$pvalue, main = "gastroc")

hist(res.soleus$pvalue, main = "soleus")

Volcano Plot

volcanoPlot <- function(result, se, pCutoff = 0.05, FCutoff = 1, tissue = character()) {
  gene_names <-
    rowData(se)[rownames(result), c("gene_name"), drop = F]
  results.df <- result %>%
    as.data.frame() %>%
    dplyr::arrange(padj)
  
  # top 10 gene labels for respectively up and down regulation
  labs.up <- results.df[results.df$log2FoldChange > FCutoff, ] %>%
    rownames() %>% .[1:10] %>% gene_names[., c("gene_name")]
  labs.down <- results.df[results.df$log2FoldChange < -FCutoff, ] %>%
    rownames() %>% .[1:10] %>% gene_names[., c("gene_name")]
  selectLab <- c(labs.up, labs.down, "Nfe2l1") %>% unique() # always including "Nfe2l1"
  
  # custom colors:
  keyvals <- ifelse(
    result$log2FoldChange < -FCutoff &
      result$padj < pCutoff,
    'royalblue',
    ifelse(
      result$log2FoldChange > FCutoff &
        result$padj < pCutoff,
      'red',
      'gray'
    )
  )
  keyvals[is.na(keyvals)] <- 'gray'
  names(keyvals)[keyvals == 'red'] <- 'up regulated'
  names(keyvals)[keyvals == 'gray'] <- 'nonsignificant'
  names(keyvals)[keyvals == 'royalblue'] <- 'down regulated'
  
  vlc.plt <- EnhancedVolcano(
    result,
    x = 'log2FoldChange',
    y = 'padj',
    title = 'WT vs KO: Nfe2l1 knockout',
    subtitle = ifelse(isEmpty(tissue), "", paste0('tissue: ', tissue)),
    caption = "",
    ylab = expression(paste(-Log[10], p[adj])),
    pCutoff = pCutoff,
    FCcutoff = FCutoff,
    legendPosition = 'right',
    pointSize = 2,
    colCustom = keyvals,
    lab = gene_names$gene_name,
    selectLab = selectLab,
    labSize = 3,
    boxedLabels = TRUE,
    drawConnectors = TRUE,
    max.overlaps = Inf
  )
  
  return(vlc.plt)
}


volcanoPlot(res.gastroc, se.gastroc, pCutoff = pCutoff, FCutoff = FCutoff, tissue = "gastrocnemius")


volcanoPlot(res.soleus, se.soleus, pCutoff = pCutoff, FCutoff = FCutoff, tissue = "soleus")

top differential expressed genes

setBold <- function(src, special_labs) {
  # source: https://stackoverflow.com/questions/39694490/highlighting-individual-axis-labels-in-bold-using-ggplot2
  if (!is.factor(src)) src <- factor(src)                   
  src_levels <- base::levels(src)                          
  brave <- special_labs %in% src_levels                   
  b_vec <- rep("plain", length(src_levels))              
  if (all(brave)) {                                     
    b_pos <- purrr::map_int(special_labs, ~which(.==src_levels))
    b_vec[b_pos] <- "bold"
    b_vec
  } else {
    message("setBold: no matching element found")
  }
  return(b_vec)
}

getTopExpressedEnsemblNames <-
  function(result,
           FCutoff,
           n,
           up = T) {
    .filter <- ifelse(up, `>`, `<`)
    
    subset(result, .filter(log2FoldChange, FCutoff)) %>%
      data.frame() %>%
      filter(baseMean > 100) %>%
      arrange(padj) %>%
      .[1:n, ] %>%
      rownames()
  }

boxplot.top <- function(result, dds, se, upregulated=TRUE, n = 20, FCutoff = 1, tissue ="") {
  # getting the top n regulated genes
  names.top <- getTopExpressedEnsemblNames(result, FCutoff=FCutoff, n=n, up = upregulated)
  
  counts.top <- counts(dds, normalized=T)[names.top, ]
  metadata <- colData(se) %>% as.data.frame()
  gene_names <- rowData(se)[, c("gene_name"), drop = F] %>% as.data.frame()
  
  counts.plt <-
    data.frame(counts.top) %>%
    tibble::rownames_to_column(var = "ensembl") %>%
    tidyr::gather(key = "samplename",
           value = "normalized_counts", 2:13) %>%
    merge(metadata, by.x="samplename", by.y=0) %>%
    merge(gene_names, by.x="ensembl", by.y=0)  %>%
    mutate(genotype = factor(genotype)) %>%
    mutate(genotype = relevel(genotype, "WT"))  %>% # "WT" needs to be displayed before "KO"
    mutate(gene_name = forcats::fct_reorder(gene_name, normalized_counts, .desc = T))
  
  direction <- ifelse(upregulated, "up", "down")
  
  ggplot(counts.plt,
         aes(
           x = as.factor(gene_name),
           y = normalized_counts,
           fill = genotype
         )) +
    geom_dotplot(
      binaxis = 'y',
      stackdir = 'center',
      dotsize = 0.3,
      position = position_dodge(0.8),
      fill = "black"
    ) +
    geom_boxplot(outlier.size = 0.3) +
    scale_y_log10() +
    xlab("Genes") +
    ylab("Normalized Counts") +
    scale_fill_manual(values = customColors) +
    ggtitle(paste0("Top ", n, " ", direction, "-regulated Genes\n tissue: ", tissue)) +
    theme_bw() +
    theme(plot.title = element_text(hjust = 0.5)) +
    theme(axis.text.x = element_text(
      angle = 45,
      hjust = 1,
      face = setBold(counts.plt$gene_name, c("Nfe2l1"))
    ))
}

gastroc

boxplot.top(res.gastroc, dds.gastroc, se.gastroc, upregulated = T, tissue = "gastroc")

boxplot.top(res.gastroc, dds.gastroc, se.gastroc, upregulated = F, tissue = "gastroc")

soleus

boxplot.top(res.soleus, dds.soleus, se.soleus, upregulated = T, tissue = "soleus")

boxplot.top(res.soleus, dds.soleus, se.soleus, upregulated = F, tissue = "soleus")

most differential genes, both tissues

using the Wald-test stat from the DESeq2 result and filtering on the set FCutoff=1 and pCutoff=0.01 yields the following plot:

gastroc_res.filtered <- apply_cutoffs(res.gastroc, colname="gastroc", FCutoff, pCutoff)
Error in apply_cutoffs(res.gastroc, colname = "gastroc", FCutoff, pCutoff) : 
  object 'res.filtered' not found

barplot

ggplot(res.combined, aes(x = diff.exp)) +
  geom_bar(aes(fill = diff.exp))

Venn/Euler-Diagram

Here are some Venn or Euler (proportional Venn) diagrams to choose from.

Note: using Eulerr diagrams, yields inconsistent plots where the circle sets will be placed at different absolute positions each time the plot function is called. => Thus the plot might need to be called multiple times until the wanted constellation is obtained.

significant overview with all genes:

venn.colors <- c(genes = "white", gastroc = "#FFB08E", soleus = "#FF6638")

gene_sets <- c(
  "all genes" = sign_gene_stats$`all genes` - sign_gene_stats$shared_sig_genes,
  "all genes&gastroc" = sign_gene_stats$gastroc - sign_gene_stats$shared_sig_genes,
  "all genes&soleus" = sign_gene_stats$soleus - sign_gene_stats$shared_sig_genes,
  "all genes&gastroc&soleus" = sign_gene_stats$shared_sig_genes
)

# 1st option: euler
plot(euler(gene_sets),
     legend = list(side = "right") ,
     main = "significant genes per tissue",
     fills = venn.colors)


# 2nd option: venn
plot(venn(gene_sets),
     # legend = list(side = "right") ,
     main = "significant genes per tissue",
     fills = venn.colors)

significant grouped:

col_palette <- RColorBrewer::brewer.pal(8, "Dark2")
# venn.colors <- c(gastroc = "#FFB08E", soleus = "#FF6638", col_palette[1:4])
venn.colors <- c(gastroc = "#FFB08E", soleus = "#FF6638", hue_pal()(4))

# sets for the venn/euler diagram (exclusive counts)
gene_sets <- c(
  "gastroc" = sign_gene_stats$gastroc - sign_gene_stats$shared_sig_genes,
  "soleus" = sign_gene_stats$soleus - sign_gene_stats$shared_sig_genes,
  "gastroc&soleus" = sign_gene_stats$shared_sig_genes,
  "gastroc&soleus&both down" = sign_gene_stats$`both down`,
  "gastroc&soleus&both up" = sign_gene_stats$`both up`,
  "gastroc&soleus&ga up, sol down" = sign_gene_stats$`ga up, sol down`,
  "gastroc&soleus&ga down, sol up" = sign_gene_stats$`ga down, sol up`
)
# 'gastroc&soleus': if this line is omitted, then all intersects will be denoted 
# with 0 (which was the goal to omit)

# 1st option: eulerr
plot(
  euler(gene_sets),
  quantities = T,
  legend = list(side = "right"),
  fills = venn.colors,
  main = "significant genes"
)



# only the two tissues
gene_sets <- c(
  "gastroc" = sign_gene_stats$gastroc - sign_gene_stats$shared_sig_genes,
  "soleus" = sign_gene_stats$soleus - sign_gene_stats$shared_sig_genes,
  "gastroc&soleus" = sign_gene_stats$shared_sig_genes
)


# 2nd option: euler two tissues
plot(
  euler(gene_sets),
  # legend = list(side = "right"),
  fills = venn.colors,
  main = "significant genes"
)



# 3rd option: venn two tissues
plot(
  venn(gene_sets),
  fills = venn.colors,
  main = "significant genes"
)

save R ojects

save(dds.gastroc, dds.soleus, res.gastroc, res.soleus, file = "./data/Robjects/03_DDS.RData")

Questions

  • should the boxplots be sorted after the counts, as they currently are?
  • what is/can be the title of the scatter plot?
LS0tCnRpdGxlOiAiMDNfREVTZXEiCmF1dGhvcjogIk5pY2sgRGllcmNrc2VuIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCnBhcmFtczoKICBGQ3V0b2ZmOgogICAgbGFiZWw6IGN1dG9mZiBmb3IgdGhlIGxvZzJGb2xkQ2hhbmdlCiAgICB2YWx1ZTogMQogIHBDdXRvZmY6CiAgICBsYWJlbDogImN1dG9mZiBmb3IgdGhlIGFkanVzdGVkIHAtVmFsdWUiCiAgICB2YWx1ZTogMC4wMQogIGtuaXR0aW5nX3BhcmFtZXRlcnNfdXNlZDoKICAgIGxhYmVsOiAiaGVscGVyIHZhcmlhYmxlIGZvciBrbml0dGluZywgc2hvdWxkIG5vdCBiZSBjaGFuZ2VkIgogICAgdmFsdWU6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQoKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShTdW1tYXJpemVkRXhwZXJpbWVudCkKbGlicmFyeShFbmhhbmNlZFZvbGNhbm8pCmxpYnJhcnkoZXVsZXJyKQpgYGAKCgpgYGB7ciBwYXJhbWV0ZXJzLCBpbmNsdWRlPUZBTFNFfQppZiAoISBleGlzdHMoImtuaXR0aW5nX3BhcmFtZXRlcnNfdXNlZCIpKSB7CiAgIyB3aGVuIGtuaXR0aW5nIHdpdGggcGFyYW1ldGVycyB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBhcmUgYWxyZWFkeSBzZXQKICBGQ3V0b2ZmIDwtIDEKICBwQ3V0b2ZmIDwtIDAuMDEKfSBlbHNlIHsKICBGQ3V0b2ZmIDwtIHBhcmFtcyRGQ3V0b2ZmCiAgcEN1dG9mZiA8LSBwYXJhbXMkcEN1dG9mZgp9IApgYGAKCgpgYGB7ciBwbG90U2V0dGluZ3MsIGluY2x1ZGU9RkFMU0V9CmN1c3RvbUNvbG9ycyA8LSBjKCJXVCI9ICIjMDBDM0M2IiwiS08iPSAiI0ZGNkM2NyIpCmBgYAoKIyBERVNlcQoKIyMgbG9hZGluZyBkYXRhCgpGb3IgdGhlIERFU2VxMiBhbmFseXNpcywgdGhlIHJhdywgcHJlZmlsdGVyZWQgY291bnRzIHdpbGwgYmUgdXNlZC4KCmBgYHtyIGxvYWRSRFN9CnNlLmdhc3Ryb2MgPC0gcmVhZFJEUygiLi9kYXRhL1JvYmplY3RzLzAxX3NlLmdhc3Ryb2MucmRzIikKc2Uuc29sZXVzIDwtIHJlYWRSRFMoIi4vZGF0YS9Sb2JqZWN0cy8wMV9zZS5zb2xldXMucmRzIikKCmNvdW50cy5nYXN0cm9jIDwtIHNlLmdhc3Ryb2Nbcm93RGF0YShzZS5nYXN0cm9jKSRmaWx0ZXJlZCxdICU+JSBhc3NheSgpCmNvdW50cy5zb2xldXMgPC0gc2Uuc29sZXVzW3Jvd0RhdGEoc2Uuc29sZXVzKSRmaWx0ZXJlZCxdICU+JSBhc3NheSgpCm1ldGFkYXRhIDwtIGNvbERhdGEoc2UuZ2FzdHJvYykKYGBgCgojIyBwcmVwYXJpbmcgRERTIG9iamVjdHMKCmBgYHtyIHByZXBERFMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNyZWF0ZUREU09iamVjdCA8LSBmdW5jdGlvbihjb3VudHMsIG1ldGFkYXRhKSB7CiAgIyBzZWxlY3Qgc2FtcGxlIGNvbHVtbnMKICByZW9yZGVyX2luZGV4IDwtIG1hdGNoKHJvd25hbWVzKG1ldGFkYXRhKSwgY29sbmFtZXMoY291bnRzKSkKICBjb3VudHMgPC0gY291bnRzWyxyZW9yZGVyX2luZGV4XQogIAogICMgQ2hlY2sgbWV0YWRhdGEgY29uc2lzdGVuY3kKICBhbGwocm93bmFtZXMobWV0YWRhdGEpICVpbiUgY29sbmFtZXMoY291bnRzKSkgJT4lCiAgICBhc3NlcnR0aGF0Ojphc3NlcnRfdGhhdCguLCBtc2cgPSAibWV0YWRhdGEgYW5kIGNvdW50IHRhYmxlIGRvIG5vdCBtYXRjaCIpCiAgCiAgIyMgREVTZXEyIG9iamVjdAogIGRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGNvdW50cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xEYXRhID0gbWV0YWRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVzaWduID0gfiBnZW5vdHlwZSkKICAKICByZXR1cm4oIERFU2VxKGRkcykgKQp9CgojIGNyZWF0aW5nIERFU2VxIE9iamVjdHMKZGRzLmdhc3Ryb2MgPC0gY3JlYXRlRERTT2JqZWN0KGNvdW50cy5nYXN0cm9jLCBtZXRhZGF0YSkKZGRzLnNvbGV1cyA8LSBjcmVhdGVERFNPYmplY3QoY291bnRzLnNvbGV1cywgbWV0YWRhdGEpCgojIGNyZWF0aW5nIFNFU2VxZyBPYmplY3RzIGZyb20gU0UKIyBkZHMuZ2FzdHJvYy5zZSA8LSBERVNlcURhdGFTZXQoc2UuZ2FzdHJvYywgZGVzaWduID0gfiBnZW5vdHlwZSkKIyBkZHMuc29sZXVzLnNlIDwtIERFU2VxRGF0YVNldChzZS5zb2xldXMsIGRlc2lnbiA9IH4gZ2Vub3R5cGUpCmBgYAoKIyBSZXN1bHRzCgpgYGB7ciByZXN1bHRzfQpyZXMuZ2FzdHJvYyA8LSByZXN1bHRzKGRkcy5nYXN0cm9jLCBhbHBoYSA9IHBDdXRvZmYsIGNvbnRyYXN0ID0gYygiZ2Vub3R5cGUiLCAiS08iLCAiV1QiKSkKcmVzLnNvbGV1cyA8LSByZXN1bHRzKGRkcy5zb2xldXMsIGFscGhhID0gcEN1dG9mZiwgY29udHJhc3QgPSBjKCJnZW5vdHlwZSIsICJLTyIsICJXVCIpKQpgYGAKCiMjIE1BLVBsb3RzCgojIyMgZ2FzdHJvYwoKYGBge3IgbWEuZ2FzdHJvYywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGxvdE1BKHJlcy5nYXN0cm9jKQpgYGAKCiMjIyBzb2xldXMKCmBgYHtyIG1hLnNvbGV1cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGxvdE1BKHJlcy5zb2xldXMpCmBgYAoKIyMgRGlzcGVyc2lvbiBFc3RpbWF0ZXMKCiMjIyBnYXN0cm9jCgpgYGB7ciBkaXNwRXN0Lmdhc3Ryb2N9CnBsb3REaXNwRXN0cyhkZHMuZ2FzdHJvYykKYGBgCgojIyMgc29sZXVzCgpgYGB7ciBkaXNwRXN0LnNvbGV1c30KcGxvdERpc3BFc3RzKGRkcy5zb2xldXMpCmBgYAoKIyMjIHRvcCBzaWduaWZpY2FudCBkaWZmLiBnZW5lcwoKYGBge3IgdG9wRGlmZkdlbmVzLmRmfQp0b3BHZW5lcy5nYXN0cm9jIDwtIGFzLmRhdGEuZnJhbWUocmVzLmdhc3Ryb2MpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJHZW5lSUQiKSAlPiUKICB0b3BfbigxMDAsIHd0ID0gLXBhZGopICU+JQogIGFycmFuZ2UocGFkaikKCnRvcEdlbmVzLnNvbGV1cyA8LSBhcy5kYXRhLmZyYW1lKHJlcy5zb2xldXMpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJHZW5lSUQiKSAlPiUKICB0b3BfbigxMDAsIHd0ID0gLXBhZGopICU+JQogIGFycmFuZ2UocGFkaikKCmtuaXRyOjprYWJsZShoZWFkKHRvcEdlbmVzLmdhc3Ryb2MpLCBjYXB0aW9uID0gImdhc3Ryb2MiKQprbml0cjo6a2FibGUoaGVhZCh0b3BHZW5lcy5zb2xldXMpLCBjYXB0aW9uID0gInNvbGV1cyIpCmBgYAoKIyMjIHAtdmFsdWVzCgpgYGB7ciBwVmFsdWVzSGlzdH0KaGlzdChyZXMuZ2FzdHJvYyRwdmFsdWUsIG1haW4gPSAiZ2FzdHJvYyIpCmhpc3QocmVzLnNvbGV1cyRwdmFsdWUsIG1haW4gPSAic29sZXVzIikKYGBgCgojIyMgVm9sY2FubyBQbG90CmBgYHtyIHZvbGNhbm9QbG90LCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gOSwgZmlnLnJldGluYSA9IDIsIGRwaSA9IDEwMH0Kdm9sY2Fub1Bsb3QgPC0gZnVuY3Rpb24ocmVzdWx0LCBzZSwgcEN1dG9mZiA9IDAuMDUsIEZDdXRvZmYgPSAxLCB0aXNzdWUgPSBjaGFyYWN0ZXIoKSkgewogIGdlbmVfbmFtZXMgPC0KICAgIHJvd0RhdGEoc2UpW3Jvd25hbWVzKHJlc3VsdCksIGMoImdlbmVfbmFtZSIpLCBkcm9wID0gRl0KICByZXN1bHRzLmRmIDwtIHJlc3VsdCAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIGRwbHlyOjphcnJhbmdlKHBhZGopCiAgCiAgIyB0b3AgMTAgZ2VuZSBsYWJlbHMgZm9yIHJlc3BlY3RpdmVseSB1cCBhbmQgZG93biByZWd1bGF0aW9uCiAgbGFicy51cCA8LSByZXN1bHRzLmRmW3Jlc3VsdHMuZGYkbG9nMkZvbGRDaGFuZ2UgPiBGQ3V0b2ZmLCBdICU+JQogICAgcm93bmFtZXMoKSAlPiUgLlsxOjEwXSAlPiUgZ2VuZV9uYW1lc1suLCBjKCJnZW5lX25hbWUiKV0KICBsYWJzLmRvd24gPC0gcmVzdWx0cy5kZltyZXN1bHRzLmRmJGxvZzJGb2xkQ2hhbmdlIDwgLUZDdXRvZmYsIF0gJT4lCiAgICByb3duYW1lcygpICU+JSAuWzE6MTBdICU+JSBnZW5lX25hbWVzWy4sIGMoImdlbmVfbmFtZSIpXQogIHNlbGVjdExhYiA8LSBjKGxhYnMudXAsIGxhYnMuZG93biwgIk5mZTJsMSIpICU+JSB1bmlxdWUoKSAjIGFsd2F5cyBpbmNsdWRpbmcgIk5mZTJsMSIKICAKICAjIGN1c3RvbSBjb2xvcnM6CiAga2V5dmFscyA8LSBpZmVsc2UoCiAgICByZXN1bHQkbG9nMkZvbGRDaGFuZ2UgPCAtRkN1dG9mZiAmCiAgICAgIHJlc3VsdCRwYWRqIDwgcEN1dG9mZiwKICAgICdyb3lhbGJsdWUnLAogICAgaWZlbHNlKAogICAgICByZXN1bHQkbG9nMkZvbGRDaGFuZ2UgPiBGQ3V0b2ZmICYKICAgICAgICByZXN1bHQkcGFkaiA8IHBDdXRvZmYsCiAgICAgICdyZWQnLAogICAgICAnZ3JheScKICAgICkKICApCiAga2V5dmFsc1tpcy5uYShrZXl2YWxzKV0gPC0gJ2dyYXknCiAgbmFtZXMoa2V5dmFscylba2V5dmFscyA9PSAncmVkJ10gPC0gJ3VwIHJlZ3VsYXRlZCcKICBuYW1lcyhrZXl2YWxzKVtrZXl2YWxzID09ICdncmF5J10gPC0gJ25vbnNpZ25pZmljYW50JwogIG5hbWVzKGtleXZhbHMpW2tleXZhbHMgPT0gJ3JveWFsYmx1ZSddIDwtICdkb3duIHJlZ3VsYXRlZCcKICAKICB2bGMucGx0IDwtIEVuaGFuY2VkVm9sY2FubygKICAgIHJlc3VsdCwKICAgIHggPSAnbG9nMkZvbGRDaGFuZ2UnLAogICAgeSA9ICdwYWRqJywKICAgIHRpdGxlID0gJ1dUIHZzIEtPOiBOZmUybDEga25vY2tvdXQnLAogICAgc3VidGl0bGUgPSBpZmVsc2UoaXNFbXB0eSh0aXNzdWUpLCAiIiwgcGFzdGUwKCd0aXNzdWU6ICcsIHRpc3N1ZSkpLAogICAgY2FwdGlvbiA9ICIiLAogICAgeWxhYiA9IGV4cHJlc3Npb24ocGFzdGUoLUxvZ1sxMF0sIHBbYWRqXSkpLAogICAgcEN1dG9mZiA9IHBDdXRvZmYsCiAgICBGQ2N1dG9mZiA9IEZDdXRvZmYsCiAgICBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcsCiAgICBwb2ludFNpemUgPSAyLAogICAgY29sQ3VzdG9tID0ga2V5dmFscywKICAgIGxhYiA9IGdlbmVfbmFtZXMkZ2VuZV9uYW1lLAogICAgc2VsZWN0TGFiID0gc2VsZWN0TGFiLAogICAgbGFiU2l6ZSA9IDMsCiAgICBib3hlZExhYmVscyA9IFRSVUUsCiAgICBkcmF3Q29ubmVjdG9ycyA9IFRSVUUsCiAgICBtYXgub3ZlcmxhcHMgPSBJbmYKICApCiAgCiAgcmV0dXJuKHZsYy5wbHQpCn0KCgp2b2xjYW5vUGxvdChyZXMuZ2FzdHJvYywgc2UuZ2FzdHJvYywgcEN1dG9mZiA9IHBDdXRvZmYsIEZDdXRvZmYgPSBGQ3V0b2ZmLCB0aXNzdWUgPSAiZ2FzdHJvY25lbWl1cyIpCgp2b2xjYW5vUGxvdChyZXMuc29sZXVzLCBzZS5zb2xldXMsIHBDdXRvZmYgPSBwQ3V0b2ZmLCBGQ3V0b2ZmID0gRkN1dG9mZiwgdGlzc3VlID0gInNvbGV1cyIpCgpgYGAKCiMjIyB0b3AgZGlmZmVyZW50aWFsIGV4cHJlc3NlZCBnZW5lcwoKYGBge3IgdG9wRGlmZkdlbmVzfQpzZXRCb2xkIDwtIGZ1bmN0aW9uKHNyYywgc3BlY2lhbF9sYWJzKSB7CiAgIyBzb3VyY2U6IGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzM5Njk0NDkwL2hpZ2hsaWdodGluZy1pbmRpdmlkdWFsLWF4aXMtbGFiZWxzLWluLWJvbGQtdXNpbmctZ2dwbG90MgogIGlmICghaXMuZmFjdG9yKHNyYykpIHNyYyA8LSBmYWN0b3Ioc3JjKSAgICAgICAgICAgICAgICAgICAKICBzcmNfbGV2ZWxzIDwtIGJhc2U6OmxldmVscyhzcmMpICAgICAgICAgICAgICAgICAgICAgICAgICAKICBicmF2ZSA8LSBzcGVjaWFsX2xhYnMgJWluJSBzcmNfbGV2ZWxzICAgICAgICAgICAgICAgICAgIAogIGJfdmVjIDwtIHJlcCgicGxhaW4iLCBsZW5ndGgoc3JjX2xldmVscykpICAgICAgICAgICAgICAKICBpZiAoYWxsKGJyYXZlKSkgeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIGJfcG9zIDwtIHB1cnJyOjptYXBfaW50KHNwZWNpYWxfbGFicywgfndoaWNoKC49PXNyY19sZXZlbHMpKQogICAgYl92ZWNbYl9wb3NdIDwtICJib2xkIgogICAgYl92ZWMKICB9IGVsc2UgewogICAgbWVzc2FnZSgic2V0Qm9sZDogbm8gbWF0Y2hpbmcgZWxlbWVudCBmb3VuZCIpCiAgfQogIHJldHVybihiX3ZlYykKfQoKZ2V0VG9wRXhwcmVzc2VkRW5zZW1ibE5hbWVzIDwtCiAgZnVuY3Rpb24ocmVzdWx0LAogICAgICAgICAgIEZDdXRvZmYsCiAgICAgICAgICAgbiwKICAgICAgICAgICB1cCA9IFQpIHsKICAgIC5maWx0ZXIgPC0gaWZlbHNlKHVwLCBgPmAsIGA8YCkKICAgIAogICAgc3Vic2V0KHJlc3VsdCwgLmZpbHRlcihsb2cyRm9sZENoYW5nZSwgRkN1dG9mZikpICU+JQogICAgICBkYXRhLmZyYW1lKCkgJT4lCiAgICAgIGZpbHRlcihiYXNlTWVhbiA+IDEwMCkgJT4lCiAgICAgIGFycmFuZ2UocGFkaikgJT4lCiAgICAgIC5bMTpuLCBdICU+JQogICAgICByb3duYW1lcygpCiAgfQoKYm94cGxvdC50b3AgPC0gZnVuY3Rpb24ocmVzdWx0LCBkZHMsIHNlLCB1cHJlZ3VsYXRlZD1UUlVFLCBuID0gMjAsIEZDdXRvZmYgPSAxLCB0aXNzdWUgPSIiKSB7CiAgIyBnZXR0aW5nIHRoZSB0b3AgbiByZWd1bGF0ZWQgZ2VuZXMKICBuYW1lcy50b3AgPC0gZ2V0VG9wRXhwcmVzc2VkRW5zZW1ibE5hbWVzKHJlc3VsdCwgRkN1dG9mZj1GQ3V0b2ZmLCBuPW4sIHVwID0gdXByZWd1bGF0ZWQpCiAgCiAgY291bnRzLnRvcCA8LSBjb3VudHMoZGRzLCBub3JtYWxpemVkPVQpW25hbWVzLnRvcCwgXQogIG1ldGFkYXRhIDwtIGNvbERhdGEoc2UpICU+JSBhcy5kYXRhLmZyYW1lKCkKICBnZW5lX25hbWVzIDwtIHJvd0RhdGEoc2UpWywgYygiZ2VuZV9uYW1lIiksIGRyb3AgPSBGXSAlPiUgYXMuZGF0YS5mcmFtZSgpCiAgCiAgY291bnRzLnBsdCA8LQogICAgZGF0YS5mcmFtZShjb3VudHMudG9wKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJlbnNlbWJsIikgJT4lCiAgICB0aWR5cjo6Z2F0aGVyKGtleSA9ICJzYW1wbGVuYW1lIiwKICAgICAgICAgICB2YWx1ZSA9ICJub3JtYWxpemVkX2NvdW50cyIsIDI6MTMpICU+JQogICAgbWVyZ2UobWV0YWRhdGEsIGJ5Lng9InNhbXBsZW5hbWUiLCBieS55PTApICU+JQogICAgbWVyZ2UoZ2VuZV9uYW1lcywgYnkueD0iZW5zZW1ibCIsIGJ5Lnk9MCkgICU+JQogICAgbXV0YXRlKGdlbm90eXBlID0gZmFjdG9yKGdlbm90eXBlKSkgJT4lCiAgICBtdXRhdGUoZ2Vub3R5cGUgPSByZWxldmVsKGdlbm90eXBlLCAiV1QiKSkgICU+JSAjICJXVCIgbmVlZHMgdG8gYmUgZGlzcGxheWVkIGJlZm9yZSAiS08iCiAgICBtdXRhdGUoZ2VuZV9uYW1lID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoZ2VuZV9uYW1lLCBub3JtYWxpemVkX2NvdW50cywgLmRlc2MgPSBUKSkKICAKICBkaXJlY3Rpb24gPC0gaWZlbHNlKHVwcmVndWxhdGVkLCAidXAiLCAiZG93biIpCiAgCiAgZ2dwbG90KGNvdW50cy5wbHQsCiAgICAgICAgIGFlcygKICAgICAgICAgICB4ID0gYXMuZmFjdG9yKGdlbmVfbmFtZSksCiAgICAgICAgICAgeSA9IG5vcm1hbGl6ZWRfY291bnRzLAogICAgICAgICAgIGZpbGwgPSBnZW5vdHlwZQogICAgICAgICApKSArCiAgICBnZW9tX2RvdHBsb3QoCiAgICAgIGJpbmF4aXMgPSAneScsCiAgICAgIHN0YWNrZGlyID0gJ2NlbnRlcicsCiAgICAgIGRvdHNpemUgPSAwLjMsCiAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC44KSwKICAgICAgZmlsbCA9ICJibGFjayIKICAgICkgKwogICAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDAuMykgKwogICAgc2NhbGVfeV9sb2cxMCgpICsKICAgIHhsYWIoIkdlbmVzIikgKwogICAgeWxhYigiTm9ybWFsaXplZCBDb3VudHMiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21Db2xvcnMpICsKICAgIGdndGl0bGUocGFzdGUwKCJUb3AgIiwgbiwgIiAiLCBkaXJlY3Rpb24sICItcmVndWxhdGVkIEdlbmVzXG4gdGlzc3VlOiAiLCB0aXNzdWUpKSArCiAgICB0aGVtZV9idygpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgKICAgICAgYW5nbGUgPSA0NSwKICAgICAgaGp1c3QgPSAxLAogICAgICBmYWNlID0gc2V0Qm9sZChjb3VudHMucGx0JGdlbmVfbmFtZSwgYygiTmZlMmwxIikpCiAgICApKQp9CmBgYAoKIyMjIyBnYXN0cm9jCgpgYGB7ciB0b3BEaWZmQm94Lmdhc3Ryb2MsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmJveHBsb3QudG9wKHJlcy5nYXN0cm9jLCBkZHMuZ2FzdHJvYywgc2UuZ2FzdHJvYywgdXByZWd1bGF0ZWQgPSBULCB0aXNzdWUgPSAiZ2FzdHJvYyIpCmJveHBsb3QudG9wKHJlcy5nYXN0cm9jLCBkZHMuZ2FzdHJvYywgc2UuZ2FzdHJvYywgdXByZWd1bGF0ZWQgPSBGLCB0aXNzdWUgPSAiZ2FzdHJvYyIpCmBgYAoKIyMjIyBzb2xldXMKCmBgYHtyIHRvcERpZmZCb3guc29sZXVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpib3hwbG90LnRvcChyZXMuc29sZXVzLCBkZHMuc29sZXVzLCBzZS5zb2xldXMsIHVwcmVndWxhdGVkID0gVCwgdGlzc3VlID0gInNvbGV1cyIpCmJveHBsb3QudG9wKHJlcy5zb2xldXMsIGRkcy5zb2xldXMsIHNlLnNvbGV1cywgdXByZWd1bGF0ZWQgPSBGLCB0aXNzdWUgPSAic29sZXVzIikKYGBgCgojIyMgbW9zdCBkaWZmZXJlbnRpYWwgZ2VuZXMsIGJvdGggdGlzc3VlcwoKdXNpbmcgdGhlIFdhbGQtdGVzdCBgc3RhdGAgZnJvbSB0aGUgREVTZXEyIHJlc3VsdCBhbmQgZmlsdGVyaW5nIG9uIHRoZSBzZXQKYEZDdXRvZmY9YGByIEZDdXRvZmZgIGFuZCBgcEN1dG9mZj1gYHIgcEN1dG9mZmAgeWllbGRzIHRoZSBmb2xsb3dpbmcgcGxvdDoKYGBge3IgdG9wRGlmZi5ib3RoLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gOSwgZmlnLnJldGluYSA9IDIsIGRwaSA9IDEwMH0KYXBwbHlfY3V0b2ZmcyA8LSBmdW5jdGlvbihkZXNlcS5yZXN1bHQsIGNvbG5hbWU9InN0YXQiLCBGQ3V0b2ZmLCBwQ3V0b2ZmKSB7CiAgcmVzLmZpbHRlcmVkIDwtIGRlc2VxLnJlc3VsdCAlPiUKICAgIGRhdGEuZnJhbWUoKSAlPiUKICAgIGZpbHRlcihwYWRqIDwgcEN1dG9mZiwKICAgICAgICAgICBsb2cyRm9sZENoYW5nZSA+IEZDdXRvZmYgfCBsb2cyRm9sZENoYW5nZSA8IC1GQ3V0b2ZmKSAlPiUKICAgIGRwbHlyOjpyZW5hbWUoISFjb2xuYW1lIDo9IHN0YXQpICU+JQogICAgZHBseXI6OnNlbGVjdCghIWNvbG5hbWUpCiAgcmV0dXJuKHJlcy5maWx0ZXJlZCkKfQoKZ2FzdHJvY19yZXMuZmlsdGVyZWQgPC0gYXBwbHlfY3V0b2ZmcyhyZXMuZ2FzdHJvYywgY29sbmFtZT0iZ2FzdHJvYyIsIEZDdXRvZmYsIHBDdXRvZmYpCnNvbGV1c19yZXMuZmlsdGVyZWQgIDwtIGFwcGx5X2N1dG9mZnMocmVzLnNvbGV1cywgIGNvbG5hbWU9InNvbGV1cyIsICBGQ3V0b2ZmLCBwQ3V0b2ZmKQoKZ2VuZV9uYW1lcyA8LSByb3dEYXRhKHNlLmdhc3Ryb2MpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoZ2VuZV9uYW1lKQoKIyBjb21iaW5pbmcgV2FsZC1UZXN0IGRhdGEgZnJvbSBib3RoIHRpc3N1ZXMgYW5kIG9yZGVyaW5nIGluIHF1YWRyYW50cwpyZXMuY29tYmluZWQgPC0gbWVyZ2UoZ2FzdHJvY19yZXMuZmlsdGVyZWQsCiAgICAgICAgICAgICAgICAgICAgICBzb2xldXNfcmVzLmZpbHRlcmVkLAogICAgICAgICAgICAgICAgICAgICAgYnkgPSAwKSAlPiUKICBtdXRhdGUoZGlmZi5leHAgPSBjYXNlX3doZW4oCiAgICBnYXN0cm9jIDwgMCAmIHNvbGV1cyA8IDAgfiAiYm90aCBkb3duIiwKICAgIGdhc3Ryb2MgPiAwICYgc29sZXVzID4gMCB+ICJib3RoIHVwIiwKICAgIGdhc3Ryb2MgPCAwICYgc29sZXVzID4gMCB+ICJnYSBkb3duLCBzb2wgdXAiLAogICAgZ2FzdHJvYyA+IDAgJiBzb2xldXMgPCAwIH4gImdhIHVwLCBzb2wgZG93biIsCiAgICBUUlVFIH4gImRpZmZlcmVudCIKICApKSAlPiUgCiAgbWVyZ2UoZ2VuZV9uYW1lcywgYnkueD0iUm93Lm5hbWVzIiwgYnkueT0wKQogCiMgcmVtb3ZpbmcgYWxsIGdlbmVfbmFtZXMgZXhjZXB0IHRoZSB0b3Bfbl9nZW5lcyAoc3VtIG9mIGFic29sdXRlIFdhbGQtdGVzdCBudW1iZXJzKQp0b3Bfbl9nZW5lcyA8LSAxMAp0b3BfbGFiZWxzIDwtIHJlcy5jb21iaW5lZCAlPiUKICBncm91cF9ieShkaWZmLmV4cCkgJT4lIAogIGFycmFuZ2UoZGVzYyhhYnMoZ2FzdHJvYykgKyBhYnMoc29sZXVzKSkpICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgJWluJSBjKDE6dG9wX25fZ2VuZXMpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICAuJGdlbmVfbmFtZQoKcmVzLmNvbWJpbmVkIDwtIHJlcy5jb21iaW5lZCAlPiUgCiAgbXV0YXRlKGdlbmVfbmFtZSA9IGlmZWxzZShnZW5lX25hbWUgJWluJSB0b3BfbGFiZWxzLCBnZW5lX25hbWUsICIiKSkKCiMgZmluYWwgcGxvdApnZ3Bsb3QocmVzLmNvbWJpbmVkLCBhZXMoeCA9IGdhc3Ryb2MsIHkgPSBzb2xldXMsIGxhYmVsID0gZ2VuZV9uYW1lKSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDApICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKyAKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGRpZmYuZXhwKSkgKwogICMgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsICJjaGFydHJldXNlMSIsICJiaXNxdWUiLCAicm95YWxibHVlIikpICsKICBsYWJzKHggPSAiZ2FzdHJvYyIsIHkgPSAic29sZXVzIikgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwobWF4Lm92ZXJsYXBzID0gMjApICsgCiAgZ2d0aXRsZShsYWJlbCA9ICJzdGF0IChXYWxkIHRlc3QpIikKYGBgCgpgYGB7ciBpbmNsdWRlPUZBTFNFfQojIG9idGFpbiBnZW5lIGNvdW50cyBmb3IgdGhlIHJlc3BlY3RpdmUgZ3JvdXBzIGZvciB2aXN1YWxpemluZwpzaWduX2dlbmVfc3RhdHMgPC0gbGlzdCgKICAiYWxsIGdlbmVzIiA9IG5yb3coZ2VuZV9uYW1lcyksCiAgImdhc3Ryb2MiID0gbnJvdyhnYXN0cm9jX3Jlcy5maWx0ZXJlZCksCiAgInNvbGV1cyIgPSBucm93KHNvbGV1c19yZXMuZmlsdGVyZWQpLAogICJzaGFyZWRfc2lnX2dlbmVzIiA9IG5yb3cocmVzLmNvbWJpbmVkKSwKICAiYm90aCB1cCIgPSBzdW0ocmVzLmNvbWJpbmVkJGRpZmYuZXhwID09ICJib3RoIHVwIiksCiAgImJvdGggZG93biIgPSBzdW0ocmVzLmNvbWJpbmVkJGRpZmYuZXhwID09ICJib3RoIGRvd24iKSwKICAiZ2EgdXAsIHNvbCBkb3duIiA9IHN1bShyZXMuY29tYmluZWQkZGlmZi5leHAgPT0gImdhIHVwLCBzb2wgZG93biIpLAogICJnYSBkb3duLCBzb2wgdXAiID0gc3VtKHJlcy5jb21iaW5lZCRkaWZmLmV4cCA9PSAiZ2EgZG93biwgc29sIHVwIikKKQpgYGAKCgojIyMjIGJhcnBsb3QKCmBgYHtyIGRpZmZHZW5lc0JhclBsb3R9CmdncGxvdChyZXMuY29tYmluZWQsIGFlcyh4ID0gZGlmZi5leHApKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBkaWZmLmV4cCkpCmBgYAoKCiMjIyMgVmVubi9FdWxlci1EaWFncmFtCgpIZXJlIGFyZSBzb21lIFZlbm4gb3IgRXVsZXIgKHByb3BvcnRpb25hbCBWZW5uKSBkaWFncmFtcyB0byBjaG9vc2UgZnJvbS4KCk5vdGU6IHVzaW5nIEV1bGVyciBkaWFncmFtcywgeWllbGRzIGluY29uc2lzdGVudCBwbG90cyB3aGVyZSB0aGUgY2lyY2xlIHNldHMgCndpbGwgYmUgcGxhY2VkIGF0IGRpZmZlcmVudCBhYnNvbHV0ZSBwb3NpdGlvbnMgZWFjaCB0aW1lIHRoZSBwbG90IGZ1bmN0aW9uIGlzCmNhbGxlZC4KPT4gVGh1cyB0aGUgcGxvdCBtaWdodCBuZWVkIHRvIGJlIGNhbGxlZCBtdWx0aXBsZSB0aW1lcyB1bnRpbCB0aGUgd2FudGVkIApjb25zdGVsbGF0aW9uIGlzIG9idGFpbmVkLgoKc2lnbmlmaWNhbnQgb3ZlcnZpZXcgd2l0aCBhbGwgZ2VuZXM6CmBgYHtyfQp2ZW5uLmNvbG9ycyA8LSBjKGdlbmVzID0gIndoaXRlIiwgZ2FzdHJvYyA9ICIjRkZCMDhFIiwgc29sZXVzID0gIiNGRjY2MzgiKQoKZ2VuZV9zZXRzIDwtIGMoCiAgImFsbCBnZW5lcyIgPSBzaWduX2dlbmVfc3RhdHMkYGFsbCBnZW5lc2AgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAiYWxsIGdlbmVzJmdhc3Ryb2MiID0gc2lnbl9nZW5lX3N0YXRzJGdhc3Ryb2MgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAiYWxsIGdlbmVzJnNvbGV1cyIgPSBzaWduX2dlbmVfc3RhdHMkc29sZXVzIC0gc2lnbl9nZW5lX3N0YXRzJHNoYXJlZF9zaWdfZ2VuZXMsCiAgImFsbCBnZW5lcyZnYXN0cm9jJnNvbGV1cyIgPSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcwopCgojIDFzdCBvcHRpb246IGV1bGVyCnBsb3QoZXVsZXIoZ2VuZV9zZXRzKSwKICAgICBsZWdlbmQgPSBsaXN0KHNpZGUgPSAicmlnaHQiKSAsCiAgICAgbWFpbiA9ICJzaWduaWZpY2FudCBnZW5lcyBwZXIgdGlzc3VlIiwKICAgICBmaWxscyA9IHZlbm4uY29sb3JzKQoKIyAybmQgb3B0aW9uOiB2ZW5uCnBsb3QodmVubihnZW5lX3NldHMpLAogICAgIG1haW4gPSAic2lnbmlmaWNhbnQgZ2VuZXMgcGVyIHRpc3N1ZSIsCiAgICAgZmlsbHMgPSB2ZW5uLmNvbG9ycykKYGBgCgoKCnNpZ25pZmljYW50IGdyb3VwZWQ6CgpgYGB7ciB2ZW5uRGlhZ3JhbX0KY29sX3BhbGV0dGUgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDgsICJEYXJrMiIpCiMgdmVubi5jb2xvcnMgPC0gYyhnYXN0cm9jID0gIiNGRkIwOEUiLCBzb2xldXMgPSAiI0ZGNjYzOCIsIGNvbF9wYWxldHRlWzE6NF0pCnZlbm4uY29sb3JzIDwtIGMoZ2FzdHJvYyA9ICIjRkZCMDhFIiwgc29sZXVzID0gIiNGRjY2MzgiLCBodWVfcGFsKCkoNCkpCgojIHNldHMgZm9yIHRoZSB2ZW5uL2V1bGVyIGRpYWdyYW0gKGV4Y2x1c2l2ZSBjb3VudHMpCmdlbmVfc2V0cyA8LSBjKAogICJnYXN0cm9jIiA9IHNpZ25fZ2VuZV9zdGF0cyRnYXN0cm9jIC0gc2lnbl9nZW5lX3N0YXRzJHNoYXJlZF9zaWdfZ2VuZXMsCiAgInNvbGV1cyIgPSBzaWduX2dlbmVfc3RhdHMkc29sZXVzIC0gc2lnbl9nZW5lX3N0YXRzJHNoYXJlZF9zaWdfZ2VuZXMsCiAgImdhc3Ryb2Mmc29sZXVzIiA9IHNpZ25fZ2VuZV9zdGF0cyRzaGFyZWRfc2lnX2dlbmVzLAogICJnYXN0cm9jJnNvbGV1cyZib3RoIGRvd24iID0gc2lnbl9nZW5lX3N0YXRzJGBib3RoIGRvd25gLAogICJnYXN0cm9jJnNvbGV1cyZib3RoIHVwIiA9IHNpZ25fZ2VuZV9zdGF0cyRgYm90aCB1cGAsCiAgImdhc3Ryb2Mmc29sZXVzJmdhIHVwLCBzb2wgZG93biIgPSBzaWduX2dlbmVfc3RhdHMkYGdhIHVwLCBzb2wgZG93bmAsCiAgImdhc3Ryb2Mmc29sZXVzJmdhIGRvd24sIHNvbCB1cCIgPSBzaWduX2dlbmVfc3RhdHMkYGdhIGRvd24sIHNvbCB1cGAKKQojICdnYXN0cm9jJnNvbGV1cyc6IGlmIHRoaXMgbGluZSBpcyBvbWl0dGVkLCB0aGVuIGFsbCBpbnRlcnNlY3RzIHdpbGwgYmUgZGVub3RlZCAKIyB3aXRoIDAgKHdoaWNoIHdhcyB0aGUgZ29hbCB0byBvbWl0KQoKIyAxc3Qgb3B0aW9uOiBldWxlcnIKcGxvdCgKICBldWxlcihnZW5lX3NldHMpLAogIHF1YW50aXRpZXMgPSBULAogIGxlZ2VuZCA9IGxpc3Qoc2lkZSA9ICJyaWdodCIpLAogIGZpbGxzID0gdmVubi5jb2xvcnMsCiAgbWFpbiA9ICJzaWduaWZpY2FudCBnZW5lcyIKKQoKCiMgb25seSB0aGUgdHdvIHRpc3N1ZXMKZ2VuZV9zZXRzIDwtIGMoCiAgImdhc3Ryb2MiID0gc2lnbl9nZW5lX3N0YXRzJGdhc3Ryb2MgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAic29sZXVzIiA9IHNpZ25fZ2VuZV9zdGF0cyRzb2xldXMgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAiZ2FzdHJvYyZzb2xldXMiID0gc2lnbl9nZW5lX3N0YXRzJHNoYXJlZF9zaWdfZ2VuZXMKKQoKCiMgMm5kIG9wdGlvbjogZXVsZXIgdHdvIHRpc3N1ZXMKcGxvdCgKICBldWxlcihnZW5lX3NldHMpLAogICMgbGVnZW5kID0gbGlzdChzaWRlID0gInJpZ2h0IiksCiAgZmlsbHMgPSB2ZW5uLmNvbG9ycywKICBtYWluID0gInNpZ25pZmljYW50IGdlbmVzIgopCgoKIyAzcmQgb3B0aW9uOiB2ZW5uIHR3byB0aXNzdWVzCnBsb3QoCiAgdmVubihnZW5lX3NldHMpLAogIGZpbGxzID0gdmVubi5jb2xvcnMsCiAgbWFpbiA9ICJzaWduaWZpY2FudCBnZW5lcyIKKQpgYGAKCiMgc2F2ZSBSIG9qZWN0cwoKYGBge3Igc2F2aW5nX1Jfb2JqZWN0c30Kc2F2ZShkZHMuZ2FzdHJvYywgZGRzLnNvbGV1cywgcmVzLmdhc3Ryb2MsIHJlcy5zb2xldXMsIGZpbGUgPSAiLi9kYXRhL1JvYmplY3RzLzAzX0REUy5SRGF0YSIpCmBgYAoKIyBRdWVzdGlvbnMKCi0gICBzaG91bGQgdGhlIGJveHBsb3RzIGJlIHNvcnRlZCBhZnRlciB0aGUgY291bnRzLCBhcyB0aGV5IGN1cnJlbnRseSBhcmU/Ci0gICB3aGF0IGlzL2NhbiBiZSB0aGUgdGl0bGUgb2YgdGhlIHNjYXR0ZXIgcGxvdD8K